import { axios, cloneDeep, storage, uuid } from 'g6-fn'
import { defineStore } from 'pinia'
import { computed, ref, watch } from 'vue'

import { dataManageInfoApi } from '@/services/api'
import { http } from '@/services/http'
import { getFileType, getFolderName } from '@/utils'
import PubSub from '@/utils/publish-subscribe'

import { useDownTaskStore } from './down-store'

export type FileStatus = 'wait' | 'uploading' | 'successful' | 'failed'
export interface FileInfo {
  id?: string
  file?: File | null
  fileList?: FileInfo[]
  name?: string
  webkitRelativePath?: string
  status?: FileStatus
  address?: string
  progress?: number
  message?: string
  // 编号
  bucketId?: string
  pid?: string | number
  cancelToken?: any
  fileType?: 'file' | 'directory'
  // 文件夹编号
  dirId?: string
  uploadTaskDetail?: FileTaskDetail
  // 展开
  expand?: boolean
}
export interface FileTaskDetail {
  // 总数量
  total: number
  // 成功数量
  successfulCount: number
  // 失败数量
  failedCount: number
}
export type BeforeAddFile = (file: File, options?: FileInfo) => FileInfo | undefined

export type TaskListOptions = {
  open: boolean
  key?: '1' | '2'
  showUploadFailed?: boolean
  showDownFailed?: boolean
}

export const useFileTaskStore = defineStore('fileTaskStore', () => {
  const uploadList = ref<FileInfo[]>([])
  const awaitUploadList = ref<FileInfo[]>([])
  const failedUploadList = ref<FileInfo[]>([])
  const taskListOptions = ref<TaskListOptions>({
    open: false,
    key: '1',
    showUploadFailed: false,
    showDownFailed: false
  })
  const downTaskStore = useDownTaskStore()
  const pubSub = new PubSub<'changeTaskListOptions'>()

  let timer: any = null
  const changeTaskListOptions = (options: TaskListOptions) => {
    taskListOptions.value.key = options.key || taskListOptions.value.key
    taskListOptions.value.showUploadFailed = options.showUploadFailed ?? taskListOptions.value.showUploadFailed
    taskListOptions.value.showDownFailed = options.showDownFailed ?? taskListOptions.value.showDownFailed
    taskListOptions.value.open = options.open || false
    pubSub.publish('changeTaskListOptions', taskListOptions.value)
    if (!taskListOptions.value.open) {
      clearInterval(timer)
    }
    if (taskListOptions.value.open && taskListOptions.value.key === '2') {
      clearInterval(timer)
      downTaskStore.getSyncDownList()
      timer = setInterval(() => {
        downTaskStore.getSyncDownList()
      }, 3000)
    }
  }

  /**
   * 获取上传列表
   */
  const getUploadList = computed(() => {
    return uploadList.value.map(item => {
      if (item.fileType === 'directory') {
        const total = item.fileList?.length || 0
        const fileList = item.fileList as FileInfo[]
        let successCount = 0,
          failedCount = 0,
          uploadingCount = 0,
          awaitCount = 0
        for (let index = 0; index < fileList.length; index++) {
          const element = fileList[index]
          element.status === 'successful' && successCount++
          element.status === 'failed' && failedCount++
          element.status === 'uploading' && uploadingCount++
          element.status === 'wait' && awaitCount++
        }
        // 等待上传 - 等待数量等于总数量
        // 上传中 - 上传中数量大于0且小于总数量
        // 上传失败（有失败文件） - 成功数+失败数等于总数量且失败数大于0
        // 上传完成（无失败文件） - 成功数+失败数等于总数量且失败数等于0
        const status: FileStatus =
          awaitCount === total
            ? 'wait'
            : uploadingCount > 0 && uploadingCount < total
            ? 'uploading'
            : successCount + failedCount === total && failedCount > 0
            ? 'failed'
            : successCount + failedCount === total && failedCount === 0
            ? 'successful'
            : 'wait'

        // 进度
        const progress = total === 0 ? 0 : Math.floor(((successCount + failedCount) / total) * 100)
        return {
          ...item,
          status,
          progress,
          uploadTaskDetail: {
            total: item.fileList?.length || 0,
            successfulCount: successCount || 0,
            failedCount: failedCount || 0
          }
        }
      }
      return item
    })
  })

  /**
   * 获取失败文件列表
   */
  const getFailedList = computed(() => {
    const result: FileInfo[] = []

    getUploadList.value.forEach(item => {
      const newItem = cloneDeep(item) as FileInfo
      if (newItem.fileType === 'directory') {
        newItem.fileList = newItem.fileList?.filter(file => file.status === 'failed') || []
        if (newItem.fileList?.length > 0) {
          result.push(newItem)
        }
      } else {
        newItem.status === 'failed' && result.push(newItem)
      }
    })
    return result
  })

  /**
   * 获取总文件数量
   */
  const getTotal = computed(() => {
    return uploadList.value.reduce((total, item) => {
      if (item.fileType === 'directory') {
        return total + (item?.fileList?.length || 0)
      }
      return total + 1
    }, 0)
  })

  /**
   * 获取失败文件总数
   */
  const getFailedTotal = computed(() => {
    return getFailedList.value.reduce((total, item) => {
      if (item.fileType === 'directory') {
        return total + (item?.fileList?.length || 0)
      }
      return total + 1
    }, 0)
  })

  /**
   * 文件转换为文件信息
   * @param file
   * @param options
   * @returns
   */
  const fileToFileInfo = (file: File, options?: FileInfo) => {
    return {
      id: uuid(),
      file: file,
      name: file.name,
      webkitRelativePath: file.webkitRelativePath || file.name,
      status: options?.status || 'wait',
      address: options?.address,
      progress: options?.progress || 0,
      message: options?.message,
      bucketId: options?.bucketId,
      pid: options?.pid || 0,
      cancelToken: options?.cancelToken,
      fileType: 'file',
      dirId: options?.dirId,
      expand: options?.expand ?? false
    } as FileInfo
  }

  /**
   * 文件转换为目录信息
   * @param fileList
   * @param options
   * @returns
   */
  const fileToDirectoryInfo = (fileList: File[], options?: FileInfo, beforeAddFile?: BeforeAddFile) => {
    const uid = uuid()
    const dirName = getFolderName(fileList[0]?.webkitRelativePath)
    const files: FileInfo[] = []
    for (let index = 0; index < fileList.length; index++) {
      const file = fileList[index]
      if (beforeAddFile) {
        const fileInfo = beforeAddFile(file, {
          dirId: uid,
          address: options?.address || '',
          bucketId: options?.bucketId || '',
          pid: options?.pid || '0',
          webkitRelativePath: file.webkitRelativePath || file.name,
          ...options
        })
        fileInfo && files.push(fileInfo)
        continue
      }
      const fileInfo = fileToFileInfo(file, {
        dirId: uid,
        address: options?.address || '',
        bucketId: options?.bucketId || '',
        pid: options?.pid || '0'
      })
      files.push(fileInfo)
    }

    return {
      id: uid,
      status: options?.status || 'wait',
      name: dirName,
      address: options?.address,
      progress: options?.progress || 0,
      message: options?.message,
      bucketId: options?.bucketId,
      pid: options?.pid || 0,
      cancelToken: options?.cancelToken,
      fileType: 'directory',
      dirId: undefined,
      expand: options?.expand ?? false,
      fileList: files,
      uploadTaskDetail: {
        total: files.length,
        successfulCount: 0,
        failedCount: 0
      }
    } as FileInfo
  }

  /**
   * 更新数据
   * @param fileInfo
   */
  const updateUploadListItem = (id: string, fileInfo: FileInfo) => {
    uploadList.value = uploadList.value.map(item => {
      if (item.id === id) {
        return { ...item, ...fileInfo }
      }

      return item
    })
  }

  /**
   * 新增上传文件
   * @param fileInfo
   */
  const addFile = (fileInfo: FileInfo) => {
    if (fileInfo?.fileType === 'file') {
      if (fileInfo?.status === 'wait') {
        addAwaitFile(fileInfo)
      } else if (fileInfo?.status === 'failed') {
        addFailedFile(fileInfo)
      }
      uploadList.value.push(fileInfo)
    }
  }

  /**
   * 新增多个上传文件
   * @param fileInfoList
   */
  const addFiles = (fileInfoList: Array<FileInfo>) => {
    if (fileInfoList.length === 0) {
      return
    }
    fileInfoList.forEach((fileInfo: FileInfo) => {
      addFile(fileInfo)
    })
  }

  /**
   * 新增文件夹
   * @param fileInfo
   */
  const addDirectory = (fileInfo: FileInfo) => {
    if (fileInfo?.fileType === 'directory') {
      fileInfo.fileList?.forEach((item: FileInfo) => {
        if (item?.status === 'wait') {
          addAwaitFile(item)
        } else if (item?.status === 'failed') {
          addFailedFile(item)
        }
      })
      uploadList.value.push(fileInfo)
    }
  }

  /**
   * 删除上传文件/文件夹
   * @param fileInfo
   */
  const removeFile = (fileInfo: FileInfo) => {
    if (fileInfo?.fileType === 'directory') {
      fileInfo.fileList?.forEach((item: FileInfo) => {
        if (item?.status === 'uploading') {
          item?.cancelToken?.cancel()
        } else if (item?.status === 'failed') {
          removeFailedFile(item)
        }
      })

      awaitUploadList.value = awaitUploadList.value.filter((item: FileInfo) => item.dirId !== fileInfo.id)
    } else {
      if (fileInfo?.status === 'uploading') {
        fileInfo?.cancelToken?.cancel()
      }
      if (fileInfo?.dirId) {
        uploadList.value.forEach((item: FileInfo) => {
          if (item?.id === fileInfo?.dirId) {
            item.fileList = item.fileList?.filter((item: FileInfo) => item.id !== fileInfo.id)
          }
        })
      }
      removeAwaitFile(fileInfo)
      removeFailedFile(fileInfo)
    }
    uploadList.value = uploadList.value.filter((item: FileInfo) => item.id !== fileInfo.id)
  }

  /**
   * 清空全部任务
   */
  const removeAllFile = () => {
    if (uploadList.value.length === 0) {
      return
    }
    uploadList.value.forEach(item => {
      removeFile(item)
    })
  }

  /**
   * 清空全部失败任务
   */
  const removeAllFailedFile = () => {
    if (uploadList.value.length === 0) {
      return
    }
    uploadList.value.forEach(item => {
      if (item.fileType === 'file' && item?.status === 'failed') {
        removeFile(item)
      }
      if (item.fileType === 'directory') {
        item.fileList?.forEach((item: FileInfo) => {
          if (item.fileType === 'file' && item?.status === 'failed') {
            removeFile(item)
          }
        })
      }
    })
  }

  /**
   * 新增待上传文件/文件夹
   * @param fileInfo
   */
  const addAwaitFile = (fileInfo: FileInfo) => {
    awaitUploadList.value.push(fileInfo)
  }

  /**
   * 删除待上传文件/文件夹
   * @param fileInfo
   */
  const removeAwaitFile = (fileInfo: FileInfo) => {
    awaitUploadList.value = awaitUploadList.value.filter((item: FileInfo) => item.id !== fileInfo.id)
  }

  /**
   * 新增上传失败文件/文件夹
   * @param fileInfo
   */
  const addFailedFile = (fileInfo: FileInfo) => {
    failedUploadList.value.push(fileInfo)
  }

  /**
   * 删除上传失败文件
   * @param fileInfo
   */
  const removeFailedFile = (fileInfo: FileInfo) => {
    failedUploadList.value = failedUploadList.value.filter((item: FileInfo) => item.id !== fileInfo.id)
  }

  /**
   * 删除上传失败文件夹
   */
  const removeFailedDirectory = (fileInfo: FileInfo) => {
    if (fileInfo.fileType !== 'directory') {
      return
    }
    failedUploadList.value = failedUploadList.value.filter((item: FileInfo) => item.dirId !== fileInfo.id)
    uploadList.value.forEach(item => {
      if (item?.id === fileInfo?.id) {
        item.fileList = item.fileList?.filter((item: FileInfo) => item.status !== 'failed')
      }
    })
  }

  /**
   * 上传文件
   * @param fileInfo
   */
  /* 取消请求令牌 */
  const CancelToken = axios.CancelToken
  const uploadFile = async (fileInfo: FileInfo) => {
    if (fileInfo.status !== 'wait') {
      return
    }
    fileInfo.status = 'uploading'
    const file = fileInfo.file as File

    try {
      // 获取minio地址
      const res = await dataManageInfoApi.preUploadASingleFile(
        {
          bucketId: fileInfo.bucketId,
          name: fileInfo.webkitRelativePath,
          type: getFileType(fileInfo.name as string),
          pid: fileInfo.pid
        },
        {
          isAddUid: true,
          headers: { 'hide-message': true }
        }
      )
      const { pid, presignedUrl: minioUrl } = res.data
      const source = CancelToken.source()
      fileInfo.cancelToken = source

      const config = {
        timeout: 1000 * 60 * 60 * 24, // 设置超时时长24h
        onUploadProgress: (Progress: { loaded: number; total: number }) => {
          const percents = Math.round((Progress.loaded * 100) / Progress.total)
          // 更新进度条
          fileInfo.progress = percents > 99 ? 99 : percents
        },
        cancelToken: source.token,
        headers: { 'hide-message': true }
      }
      // 定义一个对象来存储所有参数
      const uploadParams = {
        bucketId: fileInfo.bucketId,
        name: fileInfo?.webkitRelativePath,
        size: file?.size,
        type: getFileType(file?.name as string),
        pid: pid
      }
      await http.upload(minioUrl, file, config)
      // 把参数对象转换为字符串并存入 localStorage
      storage.set('uploadParams', uploadParams)
      // 通知后端上传完成
      await dataManageInfoApi.uploadASingleFile(uploadParams, { headers: { 'hide-message': true } })
      storage.remove('uploadParams')
      fileInfo.progress = 100
      fileInfo.status = 'successful'
      fileInfo.file = null
    } catch (error) {
      fileInfo.status = 'failed'
      addFailedFile(fileInfo)
      console.log(error)
    }

    awaitUploadList.value = awaitUploadList.value.filter((item: FileInfo) => item.id !== fileInfo.id)
  }

  /**
   * 重置
   */
  const reset = () => {
    taskListOptions.value = { open: false, key: '1', showUploadFailed: false, showDownFailed: false }
    removeAllFile()
  }

  /**
   * 监听待上传列表长度变化，如果有待上传文件，上传第一个文件
   */
  watch(
    () => awaitUploadList.value.length,
    value => {
      if (value === 0) {
        return
      }
      uploadFile(awaitUploadList.value[0])
    }
  )

  return {
    taskListOptions,
    changeTaskListOptions,
    getTotal,
    getFailedTotal,
    uploadList,
    awaitUploadList,
    failedUploadList,
    getUploadList,
    getFailedList,
    fileToFileInfo,
    fileToDirectoryInfo,
    updateUploadListItem,
    addFile,
    addFiles,
    addDirectory,
    removeFile,
    removeAllFile,
    removeAllFailedFile,
    addAwaitFile,
    removeAwaitFile,
    addFailedFile,
    removeFailedFile,
    removeFailedDirectory,
    uploadFile,
    reset,
    pubSub
  }
})
